//###############
//FILE : NameServerDatabase.java
//WRITER : Daniel Lowengrub, daniel_i_l, 320687098
//EXERCISE : oop ex3 2011
//DESCRIPTION:
//See below
//###############

package oop.ex3.nameserver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;

import oop.ex3.communication.SocketAddress;

/**
 * This stores all the information that the Nameserver needs. It has a map of
 * file managers and information about each one. It also contains a set of nameservers.
 * The database is thread safe so multiple threads can access the information at once
 * without causing data corruption.
 * @author daniel
 */
public class NameServerDatabase {
    private final HashMap<SocketAddress, FileManagerInfo>_fileManagerData  =
            new HashMap<SocketAddress, FileManagerInfo>();

    private final LinkedHashSet<SocketAddress> _nameServerList =
            new LinkedHashSet<SocketAddress>();

    /**
     * Check if the NS knows about a specific FM.
     * @param addr The FM address.
     * @return True iff it contains the FM.
     */
    public boolean containsFileManager(SocketAddress addr) {
        return _fileManagerData.containsKey(addr);
    }

    /**
     * Add a file to a given FM.
     * @param fileManager The file manager to update.
     * @param fileName The file to add.
     */
    public void addFileToFileManager(SocketAddress fileManager, String fileName) {
        synchronized(_fileManagerData) {
            FileManagerInfo info = _fileManagerData.get(fileManager);
            info.addFile(fileName);
        }
    }

    /**
     * Add a nameserver to the DB.
     * @param nameServer The address of the NS to add.
     */
    public void addNameServer(SocketAddress nameServer) {
        synchronized(_nameServerList) {
            _nameServerList.add(nameServer);
        }
    }

    /**
     * Add a list of nameservers to the DB.
     * @param nameServers The NS list to add.
     */
    public void addNameServerList(ArrayList<SocketAddress> nameServers) {
        synchronized(_nameServerList) {
            for (SocketAddress addr : nameServers)
                _nameServerList.add(addr);
        }
    }
    
    /**
     * Remove a file from a specific FM.
     * @param fileManager The FM to update.
     * @param fileName The file to add.
     */
    public void removeFileFromFileManager(SocketAddress fileManager, String fileName) {
        synchronized(_fileManagerData) {
            FileManagerInfo info = _fileManagerData.get(fileManager);
            info.removeFile(fileName);
        }
    }

    /**
     * Add a file manager to the database together with file information describing it.
     * @param fileManager The address of the FM to add.
     * @param info The file information to add to the new FM.
     */
    public void addFileManager(SocketAddress fileManager, FileManagerInfo info) {
        synchronized(_fileManagerData) {
            _fileManagerData.put(fileManager, info);
        }
    }

    /**
     * Remove a FM from the database.
     * @param fileManager The FM to remove.
     */
    public void removeFileManager(SocketAddress fileManager) {
        synchronized(_fileManagerData) {
            _fileManagerData.remove(fileManager);
        }
    }
    
    /**
     * Get a list of the addresses of all the file managers in the DB.
     * @return A list of file managers.
     */
    public ArrayList<SocketAddress> getFileManagers() {
        ArrayList<SocketAddress> output = new ArrayList<SocketAddress>();

        synchronized(_fileManagerData) {
        	Set<SocketAddress> fileManagers = _fileManagerData.keySet();
        	for (SocketAddress addr : fileManagers)
        		output.add(addr);
        }
        return output;
    }

    /**
     * Get a list of all the name servers in the DB.
     * @return The list of nameservers.
     */
    public ArrayList<SocketAddress> getNameServers() {
    	
    	ArrayList<SocketAddress> output = new ArrayList<SocketAddress>();
    	synchronized(_nameServerList) {
    		for (SocketAddress addr : _nameServerList)
    			output.add(addr);
    	}

        return output;
    }

    /**
     * Get a list of all the file names stored in this database.
     * @return The list of file names.
     */
    public ArrayList<String> getAllFiles() {
        ArrayList<String> output = new ArrayList<String>();
        
        synchronized(_fileManagerData) {
        	for (FileManagerInfo info : _fileManagerData.values())
        		for (String fileName : info.getFileNames())
        			if (!output.contains(fileName))
        				output.add(fileName);
        }

        return output;
    }

    /**
     * Get a list of all the file names sorted in alphabetical order.
     * @return The sorted list of file names.
     */
    public ArrayList<String> getSortedAllFiles() {
    	ArrayList<String> output = getAllFiles();
    	Collections.sort(output);
    	return output;
    }
    
    /**
     * Get a list of all the file managers that contain a specific file name.
     * @param fileName The file to look for.
     * @return The list of file managers.
     */
    public ArrayList<SocketAddress> getManagersWithFile(String fileName) {
        ArrayList<SocketAddress> output = new ArrayList<SocketAddress>();

        synchronized(_fileManagerData) {
        	for (SocketAddress fileManager : _fileManagerData.keySet()) {
        		if (_fileManagerData.get(fileManager).hasFile(fileName))
        			output.add(fileManager);
        	}
        }
        return output;
    }

}
